﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Diagnostics;

namespace Xceed.Silverlight.Data
{
  internal class JumpMoveAndFetchResult : MoveAndFetchResult
  {
    #region Constructors

    internal JumpMoveAndFetchResult(
      JumpMoveAndFetchAsyncResult asyncResult,
      Nullable<long> actualMove,
      ICollection fetchedItems,
      bool result,
      SourcePosition endPosition,
      RelativeOffset endPositionOffset,
      EstimateInt64 totalCount )
      : this(
          asyncResult.SourcePosition,
          asyncResult.ApproximateMove,
          asyncResult.ExpectedMoveCount,
          asyncResult.ExpectedFetchCount,
          actualMove,
          fetchedItems,
          result,
          endPosition,
          endPositionOffset,
          totalCount )
    {
    }

    internal JumpMoveAndFetchResult(
      SourcePosition sourcePosition,
      bool approximateMove,
      long expectedMoveCount,
      int expectedFetchCount,
      Nullable<long> actualMove,
      ICollection fetchedItems,
      bool result,
      SourcePosition endPosition,
      RelativeOffset endPositionOffset,
      EstimateInt64 totalCount )
      : base(
          actualMove,
          fetchedItems,
          approximateMove,
          expectedMoveCount,
          expectedFetchCount,
          result,
          endPositionOffset,
          totalCount )
    {
      this.Initialize(
        sourcePosition,
        approximateMove,
        expectedMoveCount,
        expectedFetchCount,
        actualMove,
        fetchedItems,
        result,
        endPosition,
        endPositionOffset,
        totalCount );
    }

    #endregion

    #region SourcePosition Property

    internal SourcePosition SourcePosition
    {
      get;
      private set;
    }

    #endregion

    #region EndPositionObject Property

    internal SourcePosition EndPositionObject
    {
      get;
      private set;
    }

    #endregion

    internal static void CoerceCountInformation(
      SourcePosition sourcePosition,
      bool approximateMove,
      int fetchUnit,
      Nullable<long> actualMove,
      ICollection fetchedItem,
      bool result,
      SourcePosition endPosition,
      ref RelativeOffset endPositionOffset,
      ref EstimateInt64 totalCount )
    {
      if( Math.Abs( fetchUnit ) != 1 )
        throw new ArgumentException( "fetchUnit must be equal to 1 or -1.", "fetchUnit" );

      if( !actualMove.HasValue && result )
        throw new InvalidOperationException( "actualMove cannot not be null if the operation was successful." );


      Nullable<long> realBeforeFirstOffset = null;
      Nullable<long> realAfterLastOffset = null;

      if( endPosition != null )
      {
        if( endPosition.IsBeforeFirst )
        {
          realBeforeFirstOffset = 0;
        }
        else if( endPosition.IsAfterLast )
        {
          realAfterLastOffset = 0;
        }
      }

      if( !approximateMove
        && sourcePosition != null
        && sourcePosition.IsOutOfBound
        && sourcePosition != endPosition
        && actualMove.HasValue )
      {
        long totalDisplacement = actualMove.Value;
        long fetchDisplacement = 0;
        if( fetchedItem != null && fetchedItem.Count > 0 )
        {
          fetchDisplacement = ( result )
            ? fetchedItem.Count - 1
            : fetchedItem.Count;

          fetchDisplacement *= fetchUnit;
        }

        long realOffset = Math.Abs( actualMove.Value + fetchDisplacement );

        if( sourcePosition.IsBeforeFirst )
        {
          Debug.Assert( endPosition == null || !endPosition.IsBeforeFirst || realOffset == 0 );
          realBeforeFirstOffset = realOffset;
        }
        else
        {
          Debug.Assert( sourcePosition.IsAfterLast );
          Debug.Assert( endPosition == null || !endPosition.IsAfterLast || realOffset == 0 );

          realAfterLastOffset = realOffset;
        }
      }

      if( realBeforeFirstOffset != null && realAfterLastOffset != null )
      {
        long realTotalCount = realBeforeFirstOffset.Value + realAfterLastOffset.Value - 1;

        if( totalCount != null && !totalCount.IsEstimate )
        {
          if( totalCount.Value != realTotalCount )
            throw new ArgumentException( "totalCount is invalid.", "totalCount" );
        }
        else
        {
          totalCount = realTotalCount;
        }
      }


      if( realBeforeFirstOffset != null || realAfterLastOffset != null )
      {
        if( endPositionOffset != null && !endPositionOffset.IsEstimate )
        {
          if( ( endPositionOffset.ReferencePosition == ReferencePoint.BeforeFirst ) && ( realBeforeFirstOffset != null ) )
          {
            if( endPositionOffset.Value != realBeforeFirstOffset.Value )
              throw new ArgumentException( "An invalid reference position was specified.", "endPositionOffset" );
          }
          else if( ( endPositionOffset.ReferencePosition == ReferencePoint.AfterLast ) && ( realAfterLastOffset != null ) )
          {
            if( endPositionOffset.Value != realAfterLastOffset.Value )
              throw new ArgumentException( "An invalid reference position was specified.", "endPositionOffset" );
          }
          else
          {
            if( totalCount != null && !totalCount.IsEstimate )
            {
              long otherRealValue = ( endPositionOffset.ReferencePosition == ReferencePoint.BeforeFirst )
                ? realAfterLastOffset.Value
                : realBeforeFirstOffset.Value;

              if( endPositionOffset.Value != ( totalCount.Value - otherRealValue + 1 ) )
                throw new InvalidOperationException( "An invalid totalCount or referencePosition was specified." );
            }
          }
        }
        else
        {
          if( realBeforeFirstOffset != null )
          {
            endPositionOffset = new RelativeOffset( realBeforeFirstOffset.Value, ReferencePoint.BeforeFirst );
          }
          else
          {
            endPositionOffset = new RelativeOffset( realAfterLastOffset.Value, ReferencePoint.AfterLast );
          }
        }
      }
    }

    protected void Initialize(
      SourcePosition sourcePosition,
      bool approximateMove,
      long expectedMoveCount,
      int expectedFetchCount,
      Nullable<long> actualMove,
      ICollection fetchedItems,
      bool result,
      SourcePosition endPosition,
      RelativeOffset endPositionOffset,
      EstimateInt64 totalCount )
    {
      JumpMoveAndFetchResult.CoerceCountInformation(
        sourcePosition,
        approximateMove,
        DataUtils.GetUnit( expectedFetchCount ),
        actualMove,
        fetchedItems,
        result,
        endPosition,
        ref endPositionOffset,
        ref totalCount );

      base.Initialize( actualMove, fetchedItems, approximateMove, expectedMoveCount, expectedFetchCount, result, endPositionOffset, totalCount );

      this.SourcePosition = sourcePosition;
      this.EndPositionObject = endPosition;
    }
  }
}
